package service

import (
	"context"
	"encoding/base64"
	"fmt"
	"mime/multipart"
	"os"
	"path/filepath"
	"strings"

	"database/sql"

	"gitee.com/hp-huiw/my-go-core/config"
	"gitee.com/hp-huiw/my-go-core/date"
	"gitee.com/hp-huiw/my-go-core/util"
	"github.com/gin-gonic/gin"
)

type FileService interface {
	UploadAvatarByBase64(ctx context.Context, userID string, originalFilename string, base64Content string) (string, error)
	UploadAvatar(userID string, ctx *gin.Context, file *multipart.FileHeader) (string, error)
	UploadFiles(ctx *gin.Context, files []*multipart.FileHeader) []string
	Delete(filename string)
}

// NewFileService creates a new instance of FileService.
func NewFileService(db *sql.DB) FileService {
	return &fileService{
		db:          db,
		userService: NewUserService(db),
	}
}

// UploadAvatarByBase64 uploads the avatar by using the file content in base64 format.
func (s *fileService) UploadAvatarByBase64(ctx context.Context, userID string, originalFilename string, base64Content string) (string, error) {
	// Generates the new filename with timestamp.
	filename := s.generateFilename(originalFilename)

	// Decodes the input base64 content.
	bytes, err := base64.StdEncoding.DecodeString(base64Content)

	if err != nil {
		return "", err
	}

	// Gets the avatar file saved path.
	uploadAvatarPath := s.getUploadedAvatarPath()

	// The file instance of new avatar.
	uploadAvatarFile, err := os.Create(filepath.Join(uploadAvatarPath, filename))

	if err != nil {
		return "", err
	}

	defer uploadAvatarFile.Close()

	// Writes the bytes to new avatar file.
	uploadAvatarFile.Write(bytes)

	// Updates user avatar in the data source and removes old avatar file.
	if err := s.updateUserAvatar(ctx, userID, uploadAvatarPath, filename); err != nil {
		return "", err
	}

	return s.userService.GetAvatarUrl(&filename), nil
}

// UploadAvatar uploads the avatar by using the uploaded file.
func (s *fileService) UploadAvatar(userID string, ctx *gin.Context, file *multipart.FileHeader) (string, error) {
	// Gets the file original file name.
	originalFilename := file.Filename

	// Generates the new filename with timestamp.
	filename := s.generateFilename(originalFilename)

	// Gets the avatar file saved path.
	uploadAvatarPath := s.getUploadedAvatarPath()

	// Saves the file to the path.
	ctx.SaveUploadedFile(file, filepath.Join(uploadAvatarPath, filename))

	// Updates user avatar in the data source and removes old avatar file.
	if err := s.updateUserAvatar(ctx, userID, uploadAvatarPath, filename); err != nil {
		return "", err
	}

	// Returns the filename as output.
	// The filename type is GString. That's why toString used here.
	return s.userService.GetAvatarUrl(&filename), nil
}

// UploadFiles is for demo purpose only at this moment.
func (*fileService) UploadFiles(ctx *gin.Context, files []*multipart.FileHeader) []string {
	newFilenames := []string{}

	for _, file := range files {
		// Gets the file original file name.
		filename := file.Filename

		// Gets the last position of '.'. This is for getting the file extension.
		lastDotIndex := strings.LastIndex(filename, ".")

		// If the filename contains extension then we need to keep it.
		if lastDotIndex > -1 {
			filename = fmt.Sprintf("%v-%v%v", date.GetCurrentDate().Format(date.LongTimestamp), util.NextRandomIntInRange(100000, 999999), filename[lastDotIndex:])
		} else {
			filename = fmt.Sprintf("%v-%v", date.GetCurrentDate().Format(date.LongTimestamp), util.NextRandomIntInRange(100000, 999999))
		}

		if err := ctx.SaveUploadedFile(file, "D:/"+filename); err == nil {
			newFilenames = append(newFilenames, filename)
		}
	}

	return newFilenames
}

// Delete deletes the avatar file.
func (s *fileService) Delete(filename string) {
	path := s.getUploadedAvatarPath()
	filePath := filepath.Join(path, filename)

	if util.PathExists(filePath) {
		os.Remove(filePath)
	}
}

// generateFilename generates the new avatar filename based on original file extension name.
func (*fileService) generateFilename(originalFilename string) string {
	// Gets the last position of '.'. This is for getting the file extension.
	lastDotIndex := strings.LastIndex(originalFilename, ".")

	// Generates the new filename with timestamp.
	filename := fmt.Sprintf("%v-%v", date.GetCurrentDate().Format(date.LongTimestamp), util.NextRandomIntInRange(100000, 999999))

	// If the filename contains extension then we need to keep it.
	if lastDotIndex > -1 {
		filename += originalFilename[lastDotIndex:]
	}

	return filename
}

// getUploadedAvatarPath gets uploaded avatar path.
func (*fileService) getUploadedAvatarPath() string {
	appConfig := config.ConfigLoader.GetAppConfig()

	// Gets the upload avatar file path.
	uploadAvatarPath := filepath.Join(appConfig.Resource.Upload.RootFilePath, appConfig.Resource.AvatarDirectoryName)

	_, err := os.Stat(uploadAvatarPath)

	// If the path does not exist then creates it.
	if os.IsNotExist(err) {
		os.MkdirAll(uploadAvatarPath, os.ModeDir)
	}

	return uploadAvatarPath
}

// updateUserAvatar updates user's avatar filename and deletes old avator file.
func (s *fileService) updateUserAvatar(ctx context.Context, userID string, uploadAvatarPath string, filename string) error {
	avatarFilename, err := s.userService.QueryAvatarFilename(ctx, userID)

	if err != nil {
		return err
	}

	// Deletes the old avatar file if needed.
	if avatarFilename != nil && !util.IsEmptyString(*avatarFilename) {
		currentAvatarFile := filepath.Join(uploadAvatarPath, *avatarFilename)

		_, err := os.Stat(currentAvatarFile)

		if !os.IsNotExist(err) {
			os.Remove(currentAvatarFile)
		}
	}

	// Saves the new avatar filename.
	err = s.userService.UpdateAvatarFilename(ctx, userID, filename)

	return err
}

// Container of FileService functions.
type fileService struct {
	db          *sql.DB
	userService UserService
}
